% Script: csv_hexcol_to_int16.m
% 目的: 从CSV中读取指定列（每个单元为变长十六进制字符串），
% 将每个十六进制字符串按16位拆分为若干个字，存入二维数组，并转换为有符号int16。

% ================== 用户参数 ==================
% CSV文件路径
csvPath = "./iladata.csv";   % <- 修改为你的CSV路径

% 列选择器: 可为从1开始的列索引或列名字符串
% 示例:
%   columnSelector = 2;
%   columnSelector = "HexData";
columnSelector = 5;                      % <- 修改为目标列索引或列名

% 可选: 转换后位宽（支持 8/16/32 位）
wordBits = 16;           % 选择 8 / 16 / 32
% 可选: 对较短行的填充值（将根据位宽自动转换类型）
padValue = int16(0);     % 将在后续根据 wordBits 统一转换类型

% 切分与对齐选项
% 行为举例
% 以字符串 12345 为例，按 4 hex/组，补齐到 8 位：%
% 左侧补零后：00012345
% groupDirection="ltr" → 组为 ["0001","2345"]
% groupDirection="rtl" → 组为 ["2345","0001"]（输出顺序为右到左）
% 如果你希望“从右开始分组，但最终输出仍按左到右（高位在前）”，可以告诉我，我会将 rtl 模式在切分后再反转输出顺序。%
% 推荐操作
% 检查你的数据字节序与对齐习惯：
% 若右端为低位，通常选 groupDirection="rtl"、padSide='left'。
% 若左端为高位，通常选 groupDirection="ltr"、padSide='right'。
% 如需小端/大端在“每字节内”再翻转（例如每 2 字节内部交换），我可以再加一个选项来处理
groupDirection = "ltr";  % "ltr" 从左到右, "rtl" 从右到左
padSide = 'left';        % 'left' 左侧补零, 'right' 右侧补零
% =====================================================

% 绘图配置（比例与数据量可调）
plotCols = [];           % 指定需要绘制的列索引，[] 表示自动选择有效列
plotMaxCols = Inf;       % 限制最多绘制的列数，例如 8；Inf 表示不限制
rowRange = [];           % 指定绘制的行范围，如 [1 100]；[] 表示全部行
downsampleStep = 1;      % 行抽样步长，>1 表示抽样（如 10 表示每10行取1个点）
yLimits = [];            % 固定Y轴范围，如 [-32768 32767]；[] 表示自动
figurePosition = [];     % 图窗位置与大小 [x y w h]；[] 表示默认

% 合并绘图配置（将指定列合并到同一个绘图）
overlayCols = [1 2];        % 需要合并到同一张图的列索引，例如 [1 3 5]；[] 表示不启用
overlayOnly = false;     % 为 true 时，仅绘制合并图，不再逐列绘图
overlayLegend = true;    % 合并图是否显示图例
overlayTitle = "words_int16 合并绘图"; % 合并图标题

% FFT 配置（每列进行 FFT 并绘图）
fftEnable = true;        % 是否启用每列FFT绘图
fftN = [];               % FFT长度，[] 使用 nextpow2 的长度
fs = [];                 % 采样频率(Hz)，[] 则使用归一化频率 (cycles/sample)
freqLimits = [];         % 频率显示范围 [fmin fmax]，单位与 fs 一致；[] 自动
fftLogMag = false;       % 是否使用对数幅度(semilogy)
fftDetrend = true;       % 是否在FFT前去趋势
fftWindow = 'none';      % 窗函数: 'none' | 'hann'

% 读取表格（保留字符串类型为string，保留原始列名）
T = readtable(csvPath, "TextType", "string", "VariableNamingRule", "preserve");

% 解析目标列
if isnumeric(columnSelector)
    hexCol = T{:, columnSelector};
elseif isstring(columnSelector) || ischar(columnSelector)
    hexCol = T.(string(columnSelector));
else
    error("columnSelector must be a numeric index or a column name string.");
end

% 确保为字符串数组
hexCol = string(hexCol);

nRows = numel(hexCol);
% 第一遍: 计算每行的16位词数量
wordsPerRow = zeros(nRows, 1);

% 暂存: 每行的uint16数组存入元胞数组
rowU16 = cell(nRows, 1);

for i = 1:nRows
    s = hexCol(i);
    if strlength(s) == 0 || s == "<missing>"
        rowU16{i} = uint16([]);
        wordsPerRow(i) = 0;
        continue;
    end

    % 规范化: 去除0x/0X、空格与分隔符；仅保留十六进制字符
    s = regexprep(s, "(?i)0x", "");
    s = regexprep(s, "[^0-9A-Fa-f]", "");

    if strlength(s) == 0
        rowU16{i} = uint16([]);
        wordsPerRow(i) = 0;
        continue;
    end

        % 计算每个字的十六进制字符数（位宽/4），并按配置侧补零到整字对齐
    hexPerWord = wordBits / 4;
    if mod(hexPerWord,1) ~= 0
        error('wordBits 必须是 8/16/32 之一');
    end
    remH = mod(strlength(s), hexPerWord);
    if remH ~= 0
        s = pad(s, strlength(s) + (hexPerWord - remH), padSide, '0');
    end

    % 按配置的切分顺序将字符串按每个“整字”的十六进制字符切分
    nWords = strlength(s) / hexPerWord;
    chunks = strings(1, nWords);
    if groupDirection == "ltr"
        starts = (0:nWords-1)*hexPerWord + 1;
        stops  = (1:nWords)*hexPerWord;
        for k = 1:nWords
            chunks(k) = extractBetween(s, starts(k), stops(k));
        end
    else
        % rtl: 从右向左切分，输出顺序为右到左
        for k = 1:nWords
            stopIdx = strlength(s) - (k-1)*hexPerWord;
            startIdx = stopIdx - (hexPerWord - 1);
            chunks(k) = extractBetween(s, startIdx, stopIdx);
        end
    end

    % 转为对应位宽的无符号类型并保持位模式不变
    if nWords > 0
        switch wordBits
            case 8
                vals = uint8(hex2dec(chunks));   % 行向量
            case 16
                vals = uint16(hex2dec(chunks));  % 行向量
            case 32
                vals = uint32(hex2dec(chunks));  % 行向量（注意 hex2dec 精度足够覆盖32位）
            otherwise
                error('当前仅支持 wordBits 为 8/16/32');
        end
    else
        switch wordBits
            case 8,  vals = uint8([]);
            case 16, vals = uint16([]);
            case 32, vals = uint32([]);
        end
    end

    rowU16{i} = vals;
    wordsPerRow(i) = numel(vals);
end

maxWords = max([wordsPerRow; 0]);

% 根据位宽统一 padValue 类型
switch wordBits
    case 8,  padValue = int8(0);
    case 16, padValue = int16(0);
    case 32, padValue = int32(0);
end

% 预分配输出矩阵（有符号）并填充
switch wordBits
    case 8
        words_int = repmat(padValue, nRows, maxWords);
    case 16
        words_int = repmat(padValue, nRows, maxWords);
    case 32
        words_int = repmat(padValue, nRows, maxWords);
end

for i = 1:nRows
    if wordsPerRow(i) == 0
        continue;
    end
    uval = rowU16{i};
    % 通过typecast由对应无符号类型转为对应有符号类型以保留二进制补码
    switch wordBits
        case 8
            isgn = typecast(uval, 'int8');
        case 16
            isgn = typecast(uval, 'int16');
        case 32
            isgn = typecast(uval, 'int32');
    end
    words_int(i, 1:numel(isgn)) = isgn;
end

% 为每一列分别绘图（每列单独一个图，横轴为行索引），支持比例与数据量可调
nCols = size(words_int, 2);

% 选择要绘制的列
validCols = find(arrayfun(@(c) any(wordsPerRow >= c), 1:nCols));
if ~isempty(plotCols)
    colsToPlot = intersect(validCols, plotCols(:)');
else
    colsToPlot = validCols;
end
if isfinite(plotMaxCols)
    colsToPlot = colsToPlot(1:min(numel(colsToPlot), plotMaxCols));
end

% 选择要绘制的行索引，并应用抽样
if isempty(rowRange)
    rStart = 1; rEnd = nRows;
else
    rStart = max(1, rowRange(1));
    rEnd   = min(nRows, rowRange(end));
    if rEnd < rStart
        rStart = 1; rEnd = 0; % 空范围保护
    end
end
rowIdx = rStart:1:rEnd;
if downsampleStep > 1 && ~isempty(rowIdx)
    rowIdx = rowIdx(1:downsampleStep:end);
end

% 合并绘图：将指定列合并到同一张图
if ~isempty(overlayCols)
    mergedCols = intersect(colsToPlot, overlayCols(:)');
    if ~isempty(mergedCols) && ~isempty(rowIdx)
        f = figure('Name', overlayTitle);
        if ~isempty(figurePosition)
            set(f, 'Position', figurePosition);
        end
        hold on;
        plotted = false;
        legends = strings(1, numel(mergedCols));
        for idx = 1:numel(mergedCols)
            c = mergedCols(idx);
            y = words_int(rowIdx, c);
            if all(y == padValue)
                continue; % 跳过全为填充值的列
            end
            plot(rowIdx, y, 'LineWidth', 1.0);
            legends(idx) = sprintf('列%d', c);
            plotted = true;
        end
        hold off;
        if plotted
            grid on; xlabel('行索引'); ylabel('值'); title(overlayTitle);
            if ~isempty(yLimits) && numel(yLimits) == 2
                ylim(yLimits);
            end
            if overlayLegend
                % 去除空图例项
                legends(legends == "") = [];
                if ~isempty(legends)
                    legend(legends, 'Interpreter','none');
                end
            end
        else
            close(f); % 没有有效曲线则关闭空图
        end
    end
end

% 若仅需合并图，则跳过逐列绘图
if ~overlayOnly
for c = colsToPlot
    if isempty(rowIdx)
        continue;
    end
    y = words_int(rowIdx, c);
    if all(y == padValue)
        % 全为填充值则跳过
        continue;
    end
    f = figure('Name', sprintf('words_int 第%d列', c));
    if ~isempty(figurePosition)
        set(f, 'Position', figurePosition);
    end
    plot(rowIdx, y, 'LineWidth', 1.0);
    grid on;
    xlabel('行索引');
    ylabel('值');
    title(sprintf('words_int 第%d列', c));
    if ~isempty(yLimits) && numel(yLimits) == 2
        ylim(yLimits);
    end
end
end

% 每列进行FFT并绘图（可配置），复用列与行的选择
if fftEnable
    for c = colsToPlot
        if isempty(rowIdx)
            continue;
        end
        y = words_int(rowIdx, c);
        if all(y == padValue)
            continue; % 跳过全为填充值的列
        end
        x = double(y);
        % 去趋势
        if fftDetrend
            x = detrend(x);
        end
        % 窗函数
        if ischar(fftWindow) || isstring(fftWindow)
            wname = lower(string(fftWindow));
        else
            wname = "none";
        end
        if wname == "hann"
            M = length(x);
            w = 0.5*(1 - cos(2*pi*(0:M-1)'/(M-1))); % 手工Hann窗，避免工具箱依赖
            xw = x .* w;
        else
            xw = x;
        end
        % FFT长度
        if isempty(fftN)
            N = 2^nextpow2(length(xw));
        else
            N = double(fftN);
        end
        if N < 2
            continue;
        end
        X = fft(xw, N);
        P2 = abs(X) / length(xw);
        halfN = floor(N/2);
        P1 = P2(1:halfN+1);
        if numel(P1) > 2
            P1(2:end-1) = 2*P1(2:end-1);
        end
        if isempty(fs)
            f = (0:halfN) / N;                 % 归一化频率 cycles/sample
            fLabel = '归一化频率 (cycles/sample)';
        else
            f = (0:halfN) * (fs / N);          % 频率Hz
            fLabel = '频率 (Hz)';
        end
        ffig = figure('Name', sprintf('words_int 第%d列 FFT', c));
        if ~isempty(figurePosition)
            set(ffig, 'Position', figurePosition);
        end
        if fftLogMag
            semilogy(f, P1, 'LineWidth', 1.0);
        else
            plot(f, P1, 'LineWidth', 1.0);
        end
        grid on;
        xlabel(fLabel);
        ylabel('幅度');
        title(sprintf('words_int 第%d列 FFT', c));
        if ~isempty(freqLimits) && numel(freqLimits) == 2
            xlim(freqLimits);
        end
    end
end

% 显示简要摘要
fprintf("已处理 %d 行。每行最大字数: %d。位宽: %d 位。\n", nRows, maxWords, wordBits);

% 可选: 保存结果到MAT文件（不需要可保留注释）
% save("hex_words_int.mat", "words_int", "wordsPerRow", "wordBits");

